﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using System.Windows.Input;
using FlagLib.Extensions;
using FlagLib.Patterns.MVVM;
using FlagTunes.Core;

namespace FlagTunes.View
{
    public class MainWindowViewModel : ViewModelBase<MainWindowViewModel>
    {
        private readonly FlagTunesController controller;
        private Song selectedSong;
        private string selectedArtist = String.Empty;
        private readonly Timer timeUpdateTimer;
        private string searchPattern = String.Empty;
        private Song lastUpdatedSong;
        private bool libraryUpdateFinished;
        private Playlist selectedPlaylist;

        /// <summary>
        /// Gets or sets the selected playlist.
        /// </summary>
        /// <value>The selected playlist.</value>
        public Playlist SelectedPlaylist
        {
            get { return this.selectedPlaylist; }
            set
            {
                if (this.SelectedPlaylist != value)
                {
                    this.selectedPlaylist = value;
                    this.OnPropertyChanged(vm => vm.SelectedPlaylist);
                }
            }
        }

        /// <summary>
        /// Gets the playlists.
        /// </summary>
        public IEnumerable<Playlist> Playlists
        {
            get { return this.controller.Playlists; }
        }

        /// <summary>
        /// Gets the displayed songs.
        /// </summary>
        /// <value>The displayed songs.</value>
        public IEnumerable<Song> CurrentSongs
        {
            get { return this.controller.FilteredSongs; }
        }

        /// <summary>
        /// Gets or sets the selected song.
        /// </summary>
        /// <value>The selected song.</value>
        public Song SelectedSong
        {
            get { return this.selectedSong; }
            set
            {
                if (this.SelectedSong != value)
                {
                    this.selectedSong = value;
                    this.OnPropertyChanged(vm => vm.SelectedSong);
                }
            }
        }

        /// <summary>
        /// Gets or sets the last updated song.
        /// </summary>
        /// <value>The last updated song.</value>
        public Song LastUpdatedSong
        {
            get { return this.lastUpdatedSong; }
            set
            {
                this.lastUpdatedSong = value;
                this.OnPropertyChanged(vm => vm.LastUpdatedSong);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the library update has finished.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the library update has finished; otherwise, <c>false</c>.
        /// </value>
        public bool LibraryUpdateFinished
        {
            get { return this.libraryUpdateFinished; }
            set
            {
                if (this.LibraryUpdateFinished != value)
                {
                    this.libraryUpdateFinished = value;
                    this.OnPropertyChanged(vm => vm.LibraryUpdateFinished);
                }
            }
        }

        /// <summary>
        /// Gets or sets the player volume.
        /// </summary>
        /// <value>The player volume.</value>
        public float PlayerVolume
        {
            get { return this.controller.PlayerVolume; }
            set
            {
                if (this.PlayerVolume != value)
                {
                    this.controller.PlayerVolume = value;
                    this.OnPropertyChanged(vm => vm.PlayerVolume);
                }
            }
        }

        /// <summary>
        /// Gets the loaded song's total time.
        /// </summary>
        /// <value>The loaded song's total time.</value>
        public TimeSpan LoadedSongTotalTime
        {
            get
            {
                return this.controller.LoadedSongTotalTime.Ticks == 0
                    ? new TimeSpan(1) : this.controller.LoadedSongTotalTime;
            }
        }

        /// <summary>
        /// Gets the loaded song's elapsed time.
        /// </summary>
        /// <value>The loaded song's elapsed time.</value>
        public TimeSpan LoadedSongElapsedTime
        {
            get { return this.controller.LoadedSongElapsedTime; }
        }

        /// <summary>
        /// Gets the loaded song.
        /// </summary>
        /// <value>The loaded song.</value>
        public Song LoadedSong
        {
            get { return this.controller.LoadedSong; }
        }

        /// <summary>
        /// Gets or sets the loaded song's elapsed seconds.
        /// </summary>
        /// <value>The loaded song's elapsed seconds.</value>
        public int LoadedSongElapsedSeconds
        {
            get { return (int)this.controller.LoadedSongElapsedTime.TotalSeconds; }
            set
            {
                if (this.LoadedSongElapsedSeconds != value)
                {
                    this.controller.ChangeSongElapsedTime(value - this.LoadedSongElapsedSeconds);

                    this.UpdateTimes();
                }
            }
        }

        /// <summary>
        /// Gets or sets the search pattern.
        /// </summary>
        /// <value>
        /// The search pattern.
        /// </value>
        public string SearchPattern
        {
            get { return this.searchPattern; }
            set
            {
                if (this.searchPattern != value)
                {
                    this.searchPattern = value;
                    this.OnPropertyChanged(vm => vm.SearchPattern);

                    this.controller.FilterLoadedPlaylist(value);
                    this.OnPropertyChanged(vm => vm.CurrentSongs);
                }
            }
        }

        /// <summary>
        /// Gets the artists.
        /// </summary>
        public IEnumerable<string> Artists
        {
            get
            {
                return this.controller
                    .AllSongs
                    .GroupBy(song => song.Artist)
                    .Select(group => group.Key);
            }
        }

        /// <summary>
        /// Gets or sets the selected artist.
        /// </summary>
        /// <value>The selected artist.</value>
        public string SelectedArtist
        {
            get { return this.selectedArtist; }
            set
            {
                if (this.SelectedArtist != value)
                {
                    this.selectedArtist = value;
                    this.OnPropertyChanged(vm => vm.SelectedArtist);

                    this.controller.FilterLoadedPlaylistByArtist(value);
                    this.OnPropertyChanged(vm => vm.CurrentSongs);
                }
            }
        }

        /// <summary>
        /// Gets the total number of song that are in the library.
        /// </summary>
        /// <value>The total number of songs that are in the library.</value>
        public int LibrarySongCount
        {
            get { return this.controller.AllSongs.Count(); }
        }

        /// <summary>
        /// Gets the play command.
        /// </summary>
        public ICommand PlayCommand
        {
            get
            {
                return new RelayCommand
                (
                    param => this.PlaySong(this.selectedSong),
                    param => this.SelectedSong != null
                );
            }
        }

        /// <summary>
        /// Gets the pause command.
        /// </summary>
        public ICommand PauseCommand
        {
            get
            {
                return new RelayCommand
                (
                    param =>
                    {
                        this.controller.PauseSong();
                        this.timeUpdateTimer.Stop();
                    },
                    param => this.controller.LoadedSong != null
                );
            }
        }

        /// <summary>
        /// Gets the reset artist filter command.
        /// </summary>
        public ICommand ResetArtistFilterCommand
        {
            get
            {
                return new RelayCommand
                (
                    param =>
                    {
                        this.SelectedArtist = String.Empty;
                    }
                );
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
        /// </summary>
        public MainWindowViewModel()
        {
            this.controller = new FlagTunesController();
            this.controller.LibrarySongAdded += tunesManager_LibrarySongAdded;
            this.controller.SongChanged += tunesManager_SongChanged;
            this.controller.LibraryFillFinished += tunesManager_LibraryFillFinished;

            this.selectedSong = null;

            this.timeUpdateTimer = new Timer { AutoReset = true, Interval = 1000 };
            this.timeUpdateTimer.Elapsed += timeUpdateTimer_Elapsed;

            this.searchPattern = "Search";
        }

        /// <summary>
        /// Closes the tunes manager and releases all underlying resources.
        /// </summary>
        public void CloseTunesManager()
        {
            this.controller.Dispose();
        }

        /// <summary>
        /// Fills the library.
        /// </summary>
        /// <param name="path">The path.</param>
        public void AddSongs(string path)
        {
            this.LibraryUpdateFinished = false;
            this.controller.AddSongs(path);
        }

        /// <summary>
        /// Stops the loaded song.
        /// </summary>
        public void StopLoadedSong()
        {
            this.controller.StopSong();
            this.timeUpdateTimer.Stop();

            this.UpdateTimes();
        }

        /// <summary>
        /// Plays the or continues loaded song, dependig on the current playback state.
        /// </summary>
        public void PlayOrContinueLoadedSong()
        {
            this.controller.PlayOrContinueSong();
        }

        /// <summary>
        /// Plays a specified song.
        /// </summary>
        /// <param name="song">The song.</param>
        private void PlaySong(Song song)
        {
            song.ThrowIfNull(() => song);

            if (song != this.controller.LoadedSong)
            {
                this.controller.StopSong();
                this.controller.LoadSongFromPlayList(song);
                this.OnPropertyChanged(vm => vm.LoadedSong);
            }

            this.controller.PlaySong();

            this.timeUpdateTimer.Start();
            this.UpdateTimes();
        }

        /// <summary>
        /// Handles the Elapsed event of the timeUpdateTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
        private void timeUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            this.UpdateTimes();
        }

        /// <summary>
        /// Handles the LibrarySongAdded event of the tunesManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="FlagTunes.Core.SongEventArgs"/> instance containing the event data.</param>
        private void tunesManager_LibrarySongAdded(object sender, SongEventArgs e)
        {
            this.LastUpdatedSong = e.Song;
            this.OnPropertyChanged(vm => vm.LastUpdatedSong);

            this.OnPropertyChanged(vm => vm.LibrarySongCount);
        }

        /// <summary>
        /// Handles the LibraryFillFinished event of the tunesManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void tunesManager_LibraryFillFinished(object sender, EventArgs e)
        {
            this.LibraryUpdateFinished = true;

            this.OnPropertyChanged(vm => vm.Artists);
            this.OnPropertyChanged(vm => vm.CurrentSongs);
        }

        /// <summary>
        /// Handles the SongChanged event of the tunesManager control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="FlagTunes.Core.SongEventArgs"/> instance containing the event data.</param>
        private void tunesManager_SongChanged(object sender, SongEventArgs e)
        {
            this.SelectedSong = e.Song;
            this.OnPropertyChanged(vm => vm.LoadedSong);
        }

        /// <summary>
        /// Updates the times.
        /// </summary>
        private void UpdateTimes()
        {
            this.OnPropertyChanged(vm => vm.LoadedSongTotalTime);
            this.OnPropertyChanged(vm => vm.LoadedSongElapsedTime);
            this.OnPropertyChanged(vm => vm.LoadedSongElapsedSeconds);
        }
    }
}